package visualisation;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Cursor;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

import javax.swing.Box;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.SwingUtilities;

import main.Main;

import prefuse.Constants;
import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.assignment.DataColorAction;
import prefuse.action.layout.Layout;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.activity.Activity;
import prefuse.controls.ControlAdapter;
import prefuse.controls.FocusControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Table;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.render.PolygonRenderer;
import prefuse.render.Renderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphicsLib;
import prefuse.util.force.DragForce;
import prefuse.util.force.ForceSimulator;
import prefuse.util.force.NBodyForce;
import prefuse.util.force.SpringForce;
import prefuse.util.ui.JForcePanel;
import prefuse.util.ui.UILib;
import prefuse.visual.AggregateItem;
import prefuse.visual.AggregateTable;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import utils.SpringGravForce;
import fragment.AgentState;
import fragment.Fragment;

public class CompositionView extends JPanel {
    
    public static final String GRAPH = "graph";
    public static final String NODES = "graph.nodes";
    public static final String EDGES = "graph.edges";
    public static final String AGGR = "aggregates";
  

    private static Visualization m_vis;
    private static VisualGraph vg;
    
    public CompositionView(Graph g, String label) {
    	super(new BorderLayout());
    	
        // create a new, empty visualization for our data
        m_vis = new Visualization();
                
        // --------------------------------------------------------------------
        // set up the renderers
        
        
        // draw aggregates as polygons with curved edges
        Renderer polyR = new PolygonRenderer(Constants.POLY_TYPE_CURVE);
        ((PolygonRenderer)polyR).setCurveSlack(0.15f);
        
        LabelRenderer tr = new LabelRenderer();
        tr.setTextField(label);
        tr.setRoundedCorner(8, 8);
        
        DefaultRendererFactory drf = new DefaultRendererFactory(tr);
        drf.add("ingroup('aggregates')", polyR);
        m_vis.setRendererFactory(drf);


        // --------------------------------------------------------------------
        // register the data with a visualization
        
        // adds graph to visualization and sets renderer label field
        setGraph(g, label);
        
        // fix selected focus nodes
        TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS); 
        focusGroup.addTupleSetListener(new TupleSetListener() {
            public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem)
            {
               for ( int i=0; i<rem.length; ++i )
                    ((VisualItem)rem[i]).setFixed(false);
                for ( int i=0; i<add.length; ++i ) {
                    ((VisualItem)add[i]).setFixed(false);
                    ((VisualItem)add[i]).setFixed(true);
                }
                if ( ts.getTupleCount() == 0 ) {
                    ts.addTuple(rem[0]);
                    ((VisualItem)rem[0]).setFixed(false);
                }

            	
                m_vis.run("draw");
            }
        });
        
        
        
        // --------------------------------------------------------------------
        // create actions to process the visual data

        
        int [] paletteNodes=new int[]{
        		Color.TRANSLUCENT,ColorLib.color(Color.GRAY),ColorLib.color(Color.BLACK),ColorLib.color(Color.BLUE)
        }; 
        
        int [] paletteNodesFill=new int[]{
        		Color.TRANSLUCENT,ColorLib.rgb(200,200,255),ColorLib.rgb(200,200,255),ColorLib.rgb(200,200,255)
        }; 
        
        int [] paletteEdges=new int[]{
        		ColorLib.color(Color.BLACK),Color.TRANSLUCENT
        };
        
        ColorAction fill = new DataColorAction(NODES, "state", Constants.NOMINAL,VisualItem.FILLCOLOR,paletteNodesFill);
        fill.add(VisualItem.FIXED, ColorLib.rgb(255,100,100));
        fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255,200,125));
        
        ColorAction aFill = new ColorAction(AGGR, VisualItem.FILLCOLOR, ColorLib.gray(240));
        ColorAction aStroke = new ColorAction(AGGR, VisualItem.STROKECOLOR);
        
        aStroke.setDefaultColor(ColorLib.gray(200));
        aStroke.add("_hover", ColorLib.rgb(255,100,100));
        
        ActionList draw = new ActionList();
        draw.add(fill);
        
        draw.add(new DataColorAction(NODES, "state", Constants.NOMINAL,VisualItem.STROKECOLOR,paletteNodes));
        draw.add(new ColorAction(NODES, VisualItem.TEXTCOLOR, ColorLib.rgb(0,0,0)));
        
        draw.add(new DataColorAction(EDGES,"invisible", Constants.NOMINAL, VisualItem.FILLCOLOR, paletteEdges));
        draw.add(new DataColorAction(EDGES,"invisible", Constants.NOMINAL, VisualItem.STROKECOLOR, paletteEdges));
           
        
        ActionList animate = new ActionList(Activity.INFINITY);
        
        // Force gestion
        ForceSimulator force=new ForceSimulator();
        force.addForce(new DragForce());
        force.addForce(new SpringForce(SpringForce.DEFAULT_MIN_SPRING_COEFF, SpringForce.DEFAULT_SPRING_LENGTH));
        force.addForce(new NBodyForce(NBodyForce.DEFAULT_GRAV_CONSTANT,50f,NBodyForce.DEFAULT_THETA));
        
        /**/
        force.addForce(new SpringGravForce());
        
        ForceDirectedLayout forceLayout = new ForceDirectedLayout(GRAPH);
        forceLayout.setForceSimulator(force);
        
        animate.add(forceLayout);
        animate.add(new AggregateLayout(AGGR));
        animate.add(fill);
        animate.add(aFill);
        animate.add(aStroke); 
        animate.add(new RepaintAction());
        
        // finally, we register our ActionList with the Visualization.
        // we can later execute our Actions by invoking a method on our
        // Visualization, using the name we've chosen below.
        m_vis.putAction("draw", draw);
        m_vis.putAction("layout", animate);
        
        
        

        m_vis.runAfter("draw", "layout");
        
        
        // --------------------------------------------------------------------
        // set up a display to show the visualization
        
        Display display = new Display(m_vis);
        display.setSize(700,700);
        display.pan(350, 350);
        display.setForeground(Color.GRAY);
        display.setBackground(Color.WHITE);
        
        display.setHighQuality(!Main.lowPerf);
        
        // main display controls
        display.addControlListener(new FocusControl(1));
        display.addControlListener(new AggregateDragControl());
        display.addControlListener(new PanControl());
        display.addControlListener(new ZoomControl());
        display.addControlListener(new WheelZoomControl());
        display.addControlListener(new ZoomToFitControl());
        display.addControlListener(new NeighborHighlightControl());
        
        // --------------------------------------------------------------------        
        // launch the visualization
        
        // create a panel for editing force values
        JForcePanel fpanel = new JForcePanel(force);
                      
        fpanel.add(Box.createVerticalGlue());
        
        // create a new JSplitPane to present the interface
        JSplitPane split = new JSplitPane();
        split.setLeftComponent(fpanel);
        split.setRightComponent(display);
        split.setOneTouchExpandable(true);
        split.setContinuousLayout(false);
        split.setDividerLocation(300);
        split.setResizeWeight(0);

        
        // now we run our action list
        m_vis.run("draw");
        
        add(split);
    }
    
    public void setGraph(Graph g, String label) {
        // update labeling
        DefaultRendererFactory drf = (DefaultRendererFactory)
                                                m_vis.getRendererFactory();
        ((LabelRenderer)drf.getDefaultRenderer()).setTextField(label);
        
        // update graph
        m_vis.removeGroup(GRAPH);
        vg = m_vis.addGraph(GRAPH, g);
        m_vis.setValue(EDGES, null, VisualItem.INTERACTIVE, Boolean.FALSE);
        
//        VisualItem f = (VisualItem)vg.getNode(0);
//        m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f);
//        f.setFixed(false);
    }
    
    
    
    private static void initDataGroups() {
        
        // add visual data groups
        //VisualGraph vg = m_vis.addGraph(GRAPH, currentGraph);
        //m_vis.setInteractive(EDGES, null, false);
        //m_vis.setValue(NODES, null, VisualItem.SHAPE, new Integer(Constants.SHAPE_ELLIPSE));
        
        AggregateTable at = m_vis.addAggregates(AGGR);
        at.addColumn(VisualItem.POLYGON, float[].class);
        //at.addColumn("id", int.class);
        
        // add nodes to aggregates
        // create an aggregate for each 3-clique of nodes
        Iterator nodes = vg.nodes(); 
        AggregateItem aitem = (AggregateItem)at.addItem();
            //aitem.setInt("id", 1);
            for ( int j=0; j<4; ++j ) {
                aitem.addItem((VisualItem)nodes.next());
            }
    }
    
   
  //-------------------------------------------------------------------------
  //-------------------------------------------------------------------------
  //-------------------------------------------------------------------------
    
    private static Graph currentGraph;
    private static CompositionView currentView;
    private static HashMap<String, Node> currentCorresp;
    
    public static JPanel visualise(String label, Vector<Fragment> fragList){
    	   	
    	UILib.setPlatformLookAndFeel();
    	
    	currentGraph = new Graph(true);
    	
    	Table nodes=currentGraph.getNodeTable();
    	Table edges=currentGraph.getEdgeTable();
    	
    	nodes.addColumn("name", String.class);
    	nodes.addColumn("state", AgentState.class);
    	nodes.addColumn("legend", Boolean.class, false);
    	edges.addColumn("invisible", Boolean.class,true);
        	
      	currentGraph.addNode();
      	nodes.set(0,"name","inactive");
      	nodes.set(0, "state", AgentState.INACTIVE);
      	
      	currentGraph.addNode();
      	nodes.set(1,"name","unsatisfied");
      	nodes.set(1, "state", AgentState.UNSATISFIED);
      	
      	currentGraph.addNode();
      	nodes.set(2,"name","satisfied");
      	nodes.set(2, "state", AgentState.SATISFIED);
      	
      	currentGraph.addNode();
      	nodes.set(3,"name","legend");
      	nodes.set(3, "state", AgentState.SPECSTATE);
      	
      	currentGraph.addEdge(0, 1);
      	currentGraph.addEdge(1, 2);
      	currentGraph.addEdge(2, 0);
      	
      	currentGraph.addEdge(3, 0);
      	currentGraph.addEdge(3, 1);
      	currentGraph.addEdge(3, 2);
      	

    	
    	currentCorresp = new HashMap<String, Node>();
    	   		
    	for(int i=0;i<fragList.size();i++){
    		currentCorresp.put(fragList.get(i).getId(),currentGraph.addNode());
    		currentCorresp.get(fragList.get(i).getId()).set("name", fragList.get(i).getId());
    		currentCorresp.get(fragList.get(i).getId()).set("state", fragList.get(i).currentState());
    	}
    	    	
        currentView=new CompositionView(currentGraph, label);
        
        initDataGroups();
           	        
        return currentView;
    }
        
    
    public static void addLink(Fragment s, Fragment t){	
    	
    	synchronized(currentGraph){
    		currentGraph.addEdge(currentCorresp.get(s.getId()), currentCorresp.get(t.getId()));
    		currentGraph.getEdge(currentCorresp.get(s.getId()), currentCorresp.get(t.getId())).set("invisible",false);
    		m_vis.run("draw");

    	}
    }
    
    public static void removeLink(Fragment s, Fragment t){
    	synchronized(currentGraph){
    		currentGraph.removeEdge(currentGraph.getEdge(currentCorresp.get(s.getId()), currentCorresp.get(t.getId())));
    		m_vis.run("draw");


    	}
    }
    
    public static void changeState(String f, AgentState s){
    	synchronized(currentGraph){
    		currentCorresp.get(f).set("state", s);
    		m_vis.run("draw");


    	}
    }
} 

/**
 * Layout algorithm that computes a convex hull surrounding
 * aggregate items and saves it in the "_polygon" field.
 */
class AggregateLayout extends Layout {
    
    private int m_margin = 5; // convex hull pixel margin
    private double[] m_pts;   // buffer for computing convex hulls
    
    public AggregateLayout(String aggrGroup) {
        super(aggrGroup);
    }
    
    /**
     * @see edu.berkeley.guir.prefuse.action.Action#run(edu.berkeley.guir.prefuse.ItemRegistry, double)
     */
    public void run(double frac) {
        
        AggregateTable aggr = (AggregateTable)m_vis.getGroup(m_group);
        // do we have any  to process?
        int num = aggr.getTupleCount();
        if ( num == 0 ) return;
        
        // update buffers
        int maxsz = 0;
        for ( Iterator aggrs = aggr.tuples(); aggrs.hasNext();  )
            maxsz = Math.max(maxsz, 4*2*
                    ((AggregateItem)aggrs.next()).getAggregateSize());
        if ( m_pts == null || maxsz > m_pts.length ) {
            m_pts = new double[maxsz];
        }
        
        // compute and assign convex hull for each aggregate
        Iterator aggrs = m_vis.visibleItems(m_group);
        while ( aggrs.hasNext() ) {
            AggregateItem aitem = (AggregateItem)aggrs.next();

            int idx = 0;
            if ( aitem.getAggregateSize() == 0 ) continue;
            VisualItem item = null;
            Iterator iter = aitem.items();
            while ( iter.hasNext() ) {
                item = (VisualItem)iter.next();
                if ( item.isVisible() ) {
                    addPoint(m_pts, idx, item, m_margin);
                    idx += 2*4;
                }
            }
            // if no aggregates are visible, do nothing
            if ( idx == 0 ) continue;

            // compute convex hull
            double[] nhull = GraphicsLib.convexHull(m_pts, idx);
            
            // prepare viz attribute array
            float[]  fhull = (float[])aitem.get(VisualItem.POLYGON);
            if ( fhull == null || fhull.length < nhull.length )
                fhull = new float[nhull.length];
            else if ( fhull.length > nhull.length )
                fhull[nhull.length] = Float.NaN;
            
            // copy hull values
            for ( int j=0; j<nhull.length; j++ )
                fhull[j] = (float)nhull[j];
            aitem.set(VisualItem.POLYGON, fhull);
            aitem.setValidated(false); // force invalidation
        }
    }
    
    private static void addPoint(double[] pts, int idx, 
                                 VisualItem item, int growth)
    {
        Rectangle2D b = item.getBounds();
        double minX = (b.getMinX())-growth, minY = (b.getMinY())-growth;
        double maxX = (b.getMaxX())+growth, maxY = (b.getMaxY())+growth;
        pts[idx]   = minX; pts[idx+1] = minY;
        pts[idx+2] = minX; pts[idx+3] = maxY;
        pts[idx+4] = maxX; pts[idx+5] = minY;
        pts[idx+6] = maxX; pts[idx+7] = maxY;
    }
    
} // end of class AggregateLayout


/**
 * Interactive drag control that is "aggregate-aware"
 */
class AggregateDragControl extends ControlAdapter {

    private VisualItem activeItem;
    protected Point2D down = new Point2D.Double();
    protected Point2D temp = new Point2D.Double();
    protected boolean dragged;
    
    /**
     * Creates a new drag control that issues repaint requests as an item
     * is dragged.
     */
    public AggregateDragControl() {
    }
        
    /**
     * @see prefuse.controls.Control#itemEntered(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
     */
    public void itemEntered(VisualItem item, MouseEvent e) {
        Display d = (Display)e.getSource();
        d.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
        activeItem = item;
        if ( !(item instanceof AggregateItem) )
            setFixed(item, true);
    }
    
    /**
     * @see prefuse.controls.Control#itemExited(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
     */
    public void itemExited(VisualItem item, MouseEvent e) {
        if ( activeItem == item ) {
            activeItem = null;
            setFixed(item, false);
        }
        Display d = (Display)e.getSource();
        d.setCursor(Cursor.getDefaultCursor());
    }
    
    /**
     * @see prefuse.controls.Control#itemPressed(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
     */
    public void itemPressed(VisualItem item, MouseEvent e) {
        if (!SwingUtilities.isLeftMouseButton(e)) return;
        dragged = false;
        Display d = (Display)e.getComponent();
        d.getAbsoluteCoordinate(e.getPoint(), down);
        if ( item instanceof AggregateItem )
            setFixed(item, true);
    }
    
    /**
     * @see prefuse.controls.Control#itemReleased(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
     */
    public void itemReleased(VisualItem item, MouseEvent e) {
        if (!SwingUtilities.isLeftMouseButton(e)) return;
        if ( dragged ) {
            activeItem = null;
            setFixed(item, false);
            dragged = false;
        }            
    }
    
    /**
     * @see prefuse.controls.Control#itemDragged(prefuse.visual.VisualItem, java.awt.event.MouseEvent)
     */
    public void itemDragged(VisualItem item, MouseEvent e) {
        if (!SwingUtilities.isLeftMouseButton(e)) return;
        dragged = true;
        Display d = (Display)e.getComponent();
        d.getAbsoluteCoordinate(e.getPoint(), temp);
        double dx = temp.getX()-down.getX();
        double dy = temp.getY()-down.getY();
        
        move(item, dx, dy);
        
        down.setLocation(temp);
    }

    protected static void setFixed(VisualItem item, boolean fixed) {
        if ( item instanceof AggregateItem ) {
            Iterator items = ((AggregateItem)item).items();
            while ( items.hasNext() ) {
                setFixed((VisualItem)items.next(), fixed);
            }
        } else {
            item.setFixed(fixed);
        }
    }
    
    protected static void move(VisualItem item, double dx, double dy) {
        if ( item instanceof AggregateItem ) {
            Iterator items = ((AggregateItem)item).items();
            while ( items.hasNext() ) {
                move((VisualItem)items.next(), dx, dy);
            }
        } else {
            double x = item.getX();
            double y = item.getY();
            item.setStartX(x);  item.setStartY(y);
            item.setX(x+dx);    item.setY(y+dy);
            item.setEndX(x+dx); item.setEndY(y+dy);
        }
    }
    
} // end of class AggregateDragControl
